package com.snail.workflow.service.impl;

import cn.hutool.core.io.IoUtil;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.snail.common.core.utils.PageUtils;
import com.snail.common.core.web.page.PageResult;
import com.snail.workflow.api.domain.dto.FlowDefinitionDto;
import com.snail.workflow.api.domain.dto.FlowProcDefDto;
import com.snail.workflow.api.query.FlowProcDefQuery;
import com.snail.workflow.exception.WorkflowException;
import com.snail.workflow.factory.FlowServiceFactory;
import com.snail.workflow.mapper.FlowDefinitionMapper;
import com.snail.workflow.service.IFlowDefinitionService;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.engine.repository.Deployment;
import org.flowable.engine.repository.ProcessDefinition;
import org.flowable.image.impl.DefaultProcessDiagramGenerator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.List;

/**
 * @Description: 流程定义实现
 * @Author: Snail
 * @CreateDate: 2022/7/26 21:14
 * @Version: V1.0
 */
@Service
public class FlowDefinitionServiceImpl extends FlowServiceFactory implements IFlowDefinitionService {

    @Autowired
    private FlowDefinitionMapper flowDefinitionMapper;

    /**
     * 流程文件后缀
     */
    private static final String BPMN_FILE_SUFFIX = ".bpmn";

    /**
     * 部署流程（导入流程文件）
     *
     * @param wfName     流程名称
     * @param wfCategory 流程分类
     * @param file       流程文件
     * @return 成功/失败
     */
    @Override
    public boolean importFile(String wfName, String wfCategory, MultipartFile file) {
        try {
            deploy(wfName, wfCategory, file.getInputStream());
        } catch (IOException e) {
            log.error("流程部署失败：" + e.getMessage());
            throw new WorkflowException("流程部署失败：" + e.getMessage());
        }
        return true;
    }

    /**
     * 部署流程
     *
     * @param wfName
     * @param wfCategory
     * @param is
     */
    @Override
    public String deploy(String wfName, String wfCategory, InputStream is) throws IOException {
        try {
            Deployment deploy = repositoryService.createDeployment().addInputStream(wfName + BPMN_FILE_SUFFIX, is).name(wfName).category(wfCategory).deploy();
            ProcessDefinition definition = repositoryService.createProcessDefinitionQuery().deploymentId(deploy.getId()).singleResult();
            repositoryService.setProcessDefinitionCategory(definition.getId(), wfCategory);
            return deploy.getId();
        } catch (Exception e) {
            log.error("流程部署失败：" + e.getMessage());
            throw new WorkflowException("流程部署失败：" + e.getMessage());
        } finally {
            if (is != null) {
                is.close();
            }
        }
    }

    /**
     * 读取流程xml信息
     *
     * @param deployId 流程定义id
     * @return 流程数据
     */
    @Override
    public FlowDefinitionDto readXml(String deployId) {
        FlowDefinitionDto dto = new FlowDefinitionDto();
        InputStream inputStream;
        if("0".equals(deployId)){
            inputStream = this.getClass().getResourceAsStream("/bpmn/LeaveApply.bpmn20.xml");
            dto.setWfName("请假申请");
            dto.setWfCategory("LeaveApply");
        }else{
            ProcessDefinition definition = repositoryService.createProcessDefinitionQuery().deploymentId(deployId).singleResult();
            inputStream = repositoryService.getResourceAsStream(deployId, definition.getResourceName());
            dto.setWfName(definition.getName());
            dto.setWfCategory(definition.getCategory());
        }
        dto.setXml(IoUtil.read(inputStream, StandardCharsets.UTF_8));
        return dto;
    }

    /**
     * 读取流程图片文件
     *
     * @param deployId 流程定义id
     * @return 流程文件输入流
     */
    @Override
    public InputStream readImage(String deployId) {
        ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery().deploymentId(deployId).singleResult();
        //获得图片流
        DefaultProcessDiagramGenerator diagramGenerator = new DefaultProcessDiagramGenerator();
        BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefinition.getId());
        //输出为图片
        return diagramGenerator.generateDiagram(
                bpmnModel,
                "png",
                Collections.emptyList(),
                Collections.emptyList(),
                "宋体",
                "宋体",
                "宋体",
                null,
                1.0,
                false);
    }

    @Override
    public InputStream readImageByKey(String procDefKey) {
        ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery().processDefinitionKey(procDefKey).singleResult();
        //获得图片流
        DefaultProcessDiagramGenerator diagramGenerator = new DefaultProcessDiagramGenerator();
        BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefinition.getId());
        //输出为图片
        return diagramGenerator.generateDiagram(
                bpmnModel,
                "png",
                Collections.emptyList(),
                Collections.emptyList(),
                "宋体",
                "宋体",
                "宋体",
                null,
                1.0,
                false);
    }

    /**
     * 查询流程定义列表
     *
     * @param flowProcDefQuery 流程定义查询参数
     * @return 流程定义数据
     */
    @Override
    public PageResult<FlowProcDefDto> listFlowProcDef(FlowProcDefQuery flowProcDefQuery) {
        Page<FlowProcDefDto> page = PageUtils.buildPage(flowProcDefQuery);
        page = flowDefinitionMapper.listFlowProcDef(page, flowProcDefQuery);
        return PageUtils.pageResult(page);
    }

    @Override
    public List<FlowProcDefDto> listFlowProcDefByKey(String procDefKey) {
        return flowDefinitionMapper.listFlowProcDefByKey(procDefKey);
    }

    /**
     * 部署流程（导入流程xml）
     *
     * @param flowDefinitionDto 流程数据
     * @return 成功/失败
     */
    @Override
    public boolean importXml(FlowDefinitionDto flowDefinitionDto) {
        try {
            InputStream in = new ByteArrayInputStream(flowDefinitionDto.getXml().getBytes(StandardCharsets.UTF_8));
            deploy(flowDefinitionDto.getWfName(), flowDefinitionDto.getWfCategory(), in);
        } catch (IOException e) {
            log.error("流程部署失败：" + e.getMessage());
            throw new WorkflowException("流程部署失败：" + e.getMessage());
        }
        return true;
    }
}
